#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <hypercom.h>

#define CLOSEALL_SOCKFD for(int ctr = 0; ctr < targetct; ctr++) { close(sockfd[ctr]); }
/* this macro only useful after all sockfd are opened. */

int push(const char *dest, struct target *targets, int targetct, struct flags flg) {
	FILE *fp;
	int version, opencode, responsecode, bytesread, lastwrite, addrlen, DBG_ON = 0;
	struct sockaddr_in addr;
	uint64_t filelen;
	int64_t byteswritten;
	byte buf[1024*15]; /* 15 kb buffer */
	int fd, sockfd[targetct];
	off_t f_loc;
	
	if(flg.debug) {
		printf("dbg: debugging active\n");
		DBG_ON = 1;
	}

	for(int ctr = 0; ctr < targetct; ctr++) {
		if((sockfd[ctr] = nwconnect(targets[ctr].ip, targets[ctr].port)) < 0) {
			error("push(): could not connect to %s:%d", targets[ctr].ip, targets[ctr].port);
			for(int uctr = 0; uctr < ctr; ctr++) {
				close(sockfd[uctr]);
			}
			return -1; /* failllll */
		}
	}
	/* if((sockfd = nwconnect(ip, port)) < 0) die("nwconnect(%s, %d) failed", ip, port); */
	
	/* this gets the length of the file */
	if((filelen = getflen(dest)) == 0) {
		error("push(): could not get the file length of %s", dest);
		CLOSEALL_SOCKFD
		return -1;
	}
	if(!(fp = fopen(dest, "r"))) {
		error("push(): could not open %s for reading", dest);
		CLOSEALL_SOCKFD
		return -1;
	}
	fd = fileno(fp);
	f_loc = lseek(fd, 0, SEEK_CUR);
	
	/* authenticate, debug, and give status messages */
	if(DBG_ON) printf("dbg: passed nwconnect() and fopen() stages: f_loc=%d\n", f_loc);
	pprintf(sockfd, "100 VERSION %d\012\015", FILED_VERSION);
	if(DBG_ON) printf("dbg: sent version header via pprintf()\n");
	pscanf(sockfd, "%d VERSION %d\012\015", &opencode, &version);
	if(DBG_ON) printf("dbg: scanned version headers: opencode=%d, version=%d\n", opencode, version);
	if(opencode != 100 || version != FILED_VERSION) {
		error("push(): version mismatch");
		CLOSEALL_SOCKFD
		return -1;
	}
	pprintf(sockfd, "200 OK PUSH %s SIZE %d RANGE %d TO %d\012\015", dest, filelen, flg.start, flg.end);
	if(DBG_ON) printf("dbg: sent push request\n");
	pscanf(sockfd, "%d READY\012\015", &responsecode);
	if(DBG_ON) printf("dbg: scanned ready headers: responsecode=%d\n", responsecode);
	if(responsecode != 201) {
		error("push(): response code %d invalid", responsecode);
		CLOSEALL_SOCKFD
		return -1;
	}
	pprintf(sockfd, "202 BEGIN\012\015");
	if(DBG_ON) printf("dbg: sent begin header\n");
	
	byteswritten = 0;
	lseek(fd, flg.start, SEEK_SET);
	while((bytesread = read(fd, buf, sizeof(buf))) > 0 && ) {
		for(int ctr = 0; ctr < targetct; ctr++) {
			if((lastwrite = write(sockfd[ctr], buf, bytesread)) < 0) {
				error("push(): write failed");
				CLOSEALL_SOCKFD
				return -1;
			}
			byteswritten += lastwrite;
		}
	}
	CLOSEALL_SOCKFD
	fclose(fp);
	printf("push()ed: %s [%d bytes]\n", dest, byteswritten);
	printf("targets:\n");
	for(int ctr = 0; ctr < targetct; ctr++) {
		printf("%s:%d\n", targets[ctr].ip, targets[ctr].port);
	}
	return 1;
	/* this is the end of the transmit section. */
}


int pull(const char *dest, const char *ip, int port, struct flags flg) {
	FILE *fp;
	int version, opencode, responsecode, bytesread, addrlen, DBG_ON = 0;
	struct sockaddr_in addr;
	uint64_t filelen;
	byte buf[1024*15];
	int client, stopflag = 0, len, i, j = 0, loc = -1;
	uint64_t byteswritten = 0;
	char *ip_connected, filename[NAME_MAX], cleanfilename[NAME_MAX], dir[PATH_MAX - NAME_MAX], dest[PATH_MAX];
	
	if(flg.debug) {
		printf("dbg: debugging active\n");
		DBG_ON = 1;
	}
	
	if((sockfd = nwlisten(port)) < 0) { die("nwlisten(%d) failed", port); }
	do {
		addrlen = sizeof(struct sockaddr);
		client = accept(sockfd, (struct sockaddr *)&addr, &addrlen);
		ip_connected = inet_ntoa(addr.sin_addr);
		if(DBG_ON) printf("dbg: client %s =?= %s\n", ip_connected, ip);
		if(strcmp(ip_connected, ip) == 0) {
			if(DBG_ON) printf("dbg: reached stop condition\n");
			stopflag = 1;
		} else {
			if(DBG_ON) printf("dbg: strcmp says incorrect client\n");
			close(client);
		}
	} while(!stopflag);
	/* now that the right ip address is connected, we perform massive pwnface */
	if(DBG_ON) printf("dbg: exited the checking stage\n");
	pprintf(client, "100 VERSION %d\012\015", FILED_VERSION);
	if(DBG_ON) printf("dbg: sent version header via pprintf()\n");
	pscanf(client, "%d VERSION %d\012\015", &opencode, &version);
	if(DBG_ON) printf("dbg: scanned version headers: opencode=%d, version=%d\n", opencode, version);
	if(opencode != 100 || version != FILED_VERSION) { close(client); close(sockfd); die("version mismatch or refused connection"); }
	pscanf(client, "%d OK PUSH %s SIZE %d\012\015", &responsecode, filename, &filelen);
	if(DBG_ON) printf("dbg: scanned push request: responsecode=%d, filename=%s, size=%d\n", responsecode, filename, filelen);
	if(responsecode != 200) { close(client); close(sockfd); die("incorrect response code for push (had %d)", responsecode); }
	
	/* clean file paths off for security */
	len = strlen(filename);
	for(i = len; i >= 0 && loc == -1; i--) {
		if(filename[i] == '/') {
			loc = i;
		}
	}
	if(DBG_ON) printf("dbg: exited the clean loop: loc=%d\n", loc);
	for(i = loc + 1; i <= len; i++) {
		/* j = 0 in the assignment section */
		cleanfilename[j] = filename[i];
		j++;
	}
	if(DBG_ON) printf("dbg: exited the copy loop, cleanfilename=%s\n", cleanfilename);
	len = strlen(dest);
	strncpy(dir, dest, len);
	if(dir[len-1] != '/') {
		if(DBG_ON) printf("dbg: / add condition triggered: len=%d\n", len);
		strncat(dir, "/", sizeof(dir) - len);
	}
	strncpy(dest, dir, sizeof(dest)-sizeof(cleanfilename));
	strncat(dest, cleanfilename, sizeof(cleanfilename));
	
	if(!(fp = fopen(dest, "w"))) {
		pprintf(client, "300 I/O ERROR\012\015");
		close(client);
		close(sockfd);
		die("could not open %s for writing", dest);
	}
	int filefd = fileno(fp);
	pprintf(client, "201 READY\012\015");
	if(DBG_ON) printf("dbg: sent ready header\n");
	pscanf(client, "%d BEGIN\012\015", &responsecode);
	if(DBG_ON) printf("dbg: scanned begin header: responsecode=%d\n", responsecode);
	if(responsecode != 202) { close(client); close(sockfd); die("incorrect response to 201 READY given, no data transfer"); }
	while((bytesread = read(client, buf, sizeof(buf))) > 0 && (byteswritten <= filelen)) {
		byteswritten += write(filefd, buf, bytesread);
	}
	close(client);
	close(sockfd);
	fclose(fp);
	printf("pull()ed: %s [%d bytes]\n", dest, byteswritten);
	return 1;
}

/* return, in most cases, the length of the file in bytes: POSIX/Windows/Mac(?) ONLY */
uint64_t getflen(const char *file) {
	uint64_t cnt = 0;
	int fd;
	if((fd = open(file, O_RDONLY)) < 0) return 0;
	cnt = lseek(fd, 0, SEEK_END);
	if(DBG_ON) printf("dbg: getflen() reports: filesize=%d\n", cnt);
	close(fd);
	return cnt;
}

/* create a file and fill it with zeroes until it is a certain size.
 * this is useful for when writing files in different segments, since those
 * segments can arrive in any order.
 * 
 * if the file already exists, this function returns 0.
 * if the file was created and filled to the specified size, the function returns 1.
 * if the file an error occurs while the file was being written, the function returns -1
 *  (and deletes the file). */
int fill2size(const char *file, uint64_t size) {
	int fd;
	uint64_t writecount;
	char buf;
	if((fd = open(file, O_RDONLY)) >=0) {
		close(fd);
		return 0;
		/* the file exists. */
	}
	if((fd = creat(file, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) < 0) {
		error("fill2size(): could not create the file %s", file);
		return -1;
	}
	memset(&buf, 0, sizeof(char));
	writecount = 0;
	while(writecount < size) {
		if((writecount += write(fd, &buf, 1)) < 0) {
			error("fill2size(): file I/O error. perror follows:");
			perror("I/O Error");
			/* an error occured. since we opened the file
			 * in blocking mode, this rules out EAGAIN (write again later) */
			close(fd);
			if(unlink(file) < 0) {
				error("fatal: fill2size(): couldn't unlink the file %s. bad shit is happening.");
				/* at this point, shit is really fucked up. stop. */
				exit(1);
			}
			return -1;
		}
	}
	/* if none of that happened, we should be a-okay. */
	return 1;
}
